using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Samples.ServiceHosting.StorageClient;




namespace KlearStorage
{
    public class MemoryPersister : IPersister
    {
        private readonly IDictionary<Type, HashSet<TableStorageEntity>> _storage =
            new Dictionary<Type, HashSet<TableStorageEntity>>();


        public ICollection<TableStorageEntity> Set<TRow>() where TRow : TableStorageEntity
        {
            HashSet<TableStorageEntity> set;
            _storage.TryGetValue(typeof(TRow), out set);
            if (set == null)
            {
                set = new HashSet<TableStorageEntity>();
                _storage[typeof(TRow)] = set;
            }
            return set;
        }

        public IQueryable<TRow> Query<TRow>() where TRow : TableStorageEntity
        {
            return Set<TRow>().Cast<TRow>().AsQueryable();
        }


        public TRow SelectById<TRow>(Guid id)
            where TRow : UniversalEntity
        {
            return SelectByPartitionKey<TRow>(id.ToString());
        }

        public TRow SelectByPartitionKey<TRow>(string key) where TRow : TableStorageEntity
        {
            return SelectSingleOrDefault<TRow>(
                q => q.Where(e => e.PartitionKey == key));
        }

        public TRow SelectByRowKey<TRow>(string key) where TRow : TableStorageEntity
        {
            return SelectSingleOrDefault<TRow>(
                q => q.Where(e => e.RowKey == key));
        }

        public TRow SelectByKey<TRow>(string partitionKey, string rowKey)
            where TRow : TableStorageEntity
        {
            return SelectSingleOrDefault<TRow>(
                q => q.Where(e => e.PartitionKey == partitionKey && e.RowKey == rowKey));
        }

        public TRow SelectSingleOrDefault<TRow>(Func<IQueryable<TRow>, IQueryable<TRow>> whereClause) where TRow : TableStorageEntity
        {
            var query = from e in Query<TRow>() select e;
            if (whereClause != null)
                query = whereClause(query);
            return query.SingleOrDefault();
        }


        public IList<TRow> SelectAll<TRow>() where TRow : TableStorageEntity
        {
            return Select<TRow>(null);
        }

        public IList<TRow> Select<TRow>(Func<IQueryable<TRow>, IQueryable<TRow>> whereClause) where TRow : TableStorageEntity
        {
            var query = from e in Query<TRow>() select e;
            if (whereClause != null)
                query = whereClause(query);
            return query.ToList();
        }


        public void Insert<TRow>(params TRow[] rows) where TRow : TableStorageEntity
        {
            Insert((IList<TRow>)rows);
        }

        public void Insert<TRow>(IList<TRow> rows) where TRow : TableStorageEntity
        {
            var set = Set<TRow>();
            foreach (var row in rows)
                set.Add(row);
        }


        public void Update<TRow>(params TRow[] rows) where TRow : TableStorageEntity
        {
            Update((IList<TRow>)rows);
        }

        public void Update<TRow>(IList<TRow> rows) where TRow : TableStorageEntity
        {
            var set = Set<TRow>();
            foreach (var row in rows)
            {
                set.Remove(row); // The item with identical key
                set.Add(row);
            }
        }


        public void InsertOrUpdate<TRow>(params TRow[] rows)
            where TRow : TableStorageEntity
        {
            Insert((IList<TRow>)rows);
        }

        public void InsertOrUpdate<TRow>(IList<TRow> rows)
            where TRow : TableStorageEntity
        {
            if (rows.Count == 0)
                return; // Nothing to do
            foreach (var row in rows)
            {
                var persistedRow = SelectByKey<TRow>(row.PartitionKey, row.RowKey);
                if (persistedRow == null)
                    Insert(row);
                else
                    Update(row);
            }
        }


        public void DeleteAll<TRow>() where TRow : TableStorageEntity
        {
            Delete<TRow>(null, false);
        }

        public void Delete<TRow>(TRow row) where TRow : TableStorageEntity
        {
            Delete<TRow>(
                q => q.Where(e => e.PartitionKey == row.PartitionKey && e.RowKey == row.RowKey), true);
        }

        public void Delete<TRow>(Func<IQueryable<TRow>, IQueryable<TRow>> whereClause, bool single) where TRow : TableStorageEntity
        {
            var set = Set<TRow>();
            var rows = Select(whereClause);

            int count = 0;
            foreach (var r in rows)
            {
                if (single && count++ > 1)
                    throw new Exception(string.Format("There are multiple rows with the same key {0}#{1}. PartitionKey+RowKey should be unique.", typeof(TRow).Name, whereClause));

                set.Remove(r);
            }

            if (single && count == 0)
                throw new Exception(string.Format("There are no row to delete with the key {0}#{1}.", typeof(TRow).Name, whereClause));
        }
    }
}